#include "json_value.h"
#include "json.h"

#include <util/generic/ymath.h>
#include <util/generic/ylimits.h>
#include <util/generic/utility.h>
#include <util/generic/singleton.h>
#include <util/stream/str.h>
#include <util/stream/output.h>
#include <util/string/cast.h>
#include <util/string/type.h>
#include <util/string/vector.h>
#include <util/system/yassert.h>

static bool
AreJsonMapsEqual(const NJson::TJsonValue& lhs, const NJson::TJsonValue& rhs) {
    using namespace NJson;

    Y_VERIFY(lhs.GetType() == JSON_MAP, "lhs has not a JSON_MAP type.");

    if (rhs.GetType() != JSON_MAP)
        return false;

    typedef TJsonValue::TMap TMap;
    const TMap& lhsMap = lhs.GetMap();
    const TMap& rhsMap = rhs.GetMap();

    if (lhsMap.size() != rhsMap.size())
        return false;

    for (const auto& lhsIt : lhsMap) {
        TMap::const_iterator rhsIt = rhsMap.find(lhsIt.first);
        if (rhsIt == rhsMap.end())
            return false;

        if (lhsIt.second != rhsIt->second)
            return false;
    }

    return true;
}

static bool
AreJsonArraysEqual(const NJson::TJsonValue& lhs, const NJson::TJsonValue& rhs) {
    using namespace NJson;

    Y_VERIFY(lhs.GetType() == JSON_ARRAY, "lhs has not a JSON_ARRAY type.");

    if (rhs.GetType() != JSON_ARRAY)
        return false;

    typedef TJsonValue::TArray TArray;
    const TArray& lhsArray = lhs.GetArray();
    const TArray& rhsArray = rhs.GetArray();

    if (lhsArray.size() != rhsArray.size())
        return false;

    for (TArray::const_iterator lhsIt = lhsArray.begin(), rhsIt = rhsArray.begin();
         lhsIt != lhsArray.end(); ++lhsIt, ++rhsIt)
    {
        if (*lhsIt != *rhsIt)
            return false;
    }

    return true;
}

namespace NJson {

const TJsonValue TJsonValue::UNDEFINED;

TJsonValue::TJsonValue(const EJsonValueType type)
    : Type(JSON_UNDEFINED)
{
    SetType(type);
}

TJsonValue::TJsonValue(TJsonValue&& vval) noexcept
     : Type(vval.Type)
     , Value(std::move(vval.Value))
{
    vval.Type = JSON_UNDEFINED;
    Zero(vval.Value);
}

TJsonValue::TJsonValue(const TJsonValue &val)
{
    Type = val.Type;
    switch (Type) {
    case JSON_STRING:
        Value.String = new TString(val.GetString());
        break;
    case JSON_MAP:
        Value.Map = new TMap(val.GetMap());
        break;
    case JSON_ARRAY:
        Value.Array = new TArray(val.GetArray());
        break;
    case JSON_UNDEFINED:
    case JSON_NULL:
    case JSON_BOOLEAN:
    case JSON_INTEGER:
    case JSON_UINTEGER:
    case JSON_DOUBLE:
        Value = val.Value;
        break;
    }
}

TJsonValue& TJsonValue::operator = (const TJsonValue& val) {
    if (this == &val)
        return *this;
    TJsonValue tmp(val);
    tmp.Swap(*this);
    return *this;
}

TJsonValue& TJsonValue::operator = (TJsonValue&& val) noexcept {
    if (this == &val)
        return *this;
    TJsonValue tmp(std::move(val));
    tmp.Swap(*this);
    return *this;
}

TJsonValue::TJsonValue(const bool value) noexcept
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_BOOLEAN);
    Value.Boolean = value;
}

TJsonValue::TJsonValue(const long long value) noexcept
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_INTEGER);
    Value.Integer = value;
}

TJsonValue::TJsonValue(const unsigned long long value) noexcept
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_UINTEGER);
    Value.UInteger = value;
}

TJsonValue::TJsonValue(const int value) noexcept
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_INTEGER);
    Value.Integer = value;
}

TJsonValue::TJsonValue(const unsigned int value) noexcept
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_UINTEGER);
    Value.UInteger = value;
}

TJsonValue::TJsonValue(const long value) noexcept
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_INTEGER);
    Value.Integer = value;
}

TJsonValue::TJsonValue(const unsigned long value) noexcept
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_UINTEGER);
    Value.UInteger = value;
}

TJsonValue::TJsonValue(const double value) noexcept
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_DOUBLE);
    Value.Double = value;
}

TJsonValue::TJsonValue(const TString& value)
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_STRING);
    *Value.String = value;
}

TJsonValue::TJsonValue(TString&& value)
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_STRING);
    *Value.String = std::move(value);
}

TJsonValue::TJsonValue(const TStringBuf value)
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_STRING);
    *Value.String = value;
}

TJsonValue::TJsonValue(const char* value)
    : Type(JSON_UNDEFINED)
{
    SetType(JSON_STRING);
    *Value.String = value;
}

EJsonValueType TJsonValue::GetType() const noexcept {
    return Type;
}

TJsonValue& TJsonValue::SetType(const EJsonValueType type) {
    if (Type == type)
        return *this;

    Clear();
    Type = type;

    switch (Type) {
    case JSON_STRING:
        Value.String = new TString();
        break;
    case JSON_MAP:
        Value.Map = new TMap();
        break;
    case JSON_ARRAY:
        Value.Array = new TArray();
        break;
    case JSON_UNDEFINED:
    case JSON_NULL:
    case JSON_BOOLEAN:
    case JSON_INTEGER:
    case JSON_UINTEGER:
    case JSON_DOUBLE:
        break;
    }

    return *this;
}

TJsonValue& TJsonValue::SetValue(const TJsonValue &value) {
    return *this = value;
}

TJsonValue& TJsonValue::SetValue(TJsonValue&& value) {
    *this = std::move(value);
    return *this;
}

TJsonValue& TJsonValue::InsertValue(const TString& key, const TJsonValue& value) {
    SetType(JSON_MAP);
    return (*Value.Map)[key] = value;
}

TJsonValue& TJsonValue::InsertValue(const TStringBuf key, const TJsonValue& value) {
    SetType(JSON_MAP);
    return (*Value.Map)[key] = value;
}

TJsonValue& TJsonValue::InsertValue(const char* key, const TJsonValue& value) {
    SetType(JSON_MAP);
    return (*Value.Map)[key] = value;
}

TJsonValue& TJsonValue::InsertValue(const TString& key, TJsonValue&& value) {
    SetType(JSON_MAP);
    return (*Value.Map)[key] = std::move(value);
}

TJsonValue& TJsonValue::InsertValue(const TStringBuf key, TJsonValue&& value) {
    SetType(JSON_MAP);
    return (*Value.Map)[key] = std::move(value);
}

TJsonValue& TJsonValue::InsertValue(const char* key, TJsonValue&& value) {
    SetType(JSON_MAP);
    return (*Value.Map)[key] = std::move(value);
}

TJsonValue& TJsonValue::Back() {
    if (Type != JSON_ARRAY)
        ythrow TJsonException() << "Not an array";

    if (Value.Array->empty())
        ythrow TJsonException() << "Get back on empty array";

    return Value.Array->back();
}

TJsonValue& TJsonValue::AppendValue(const TJsonValue& value) {
    SetType(JSON_ARRAY);
    Value.Array->push_back(value);
    return Value.Array->back();
}

TJsonValue& TJsonValue::AppendValue(TJsonValue&& value) {
    SetType(JSON_ARRAY);
    Value.Array->push_back(std::move(value));
    return Value.Array->back();
}

void TJsonValue::EraseValue(const TStringBuf key) {
    if (IsMap()) {
        TMap::iterator it = Value.Map->find(key);
        if (it != Value.Map->end())
            Value.Map->erase(it);
    }
}

void TJsonValue::EraseValue(const size_t index) {
    if (IsArray()) {
        if (index >= Value.Array->size()) {
            return;
        }
        TArray::iterator it = Value.Array->begin() + index;
        Value.Array->erase(it);
    }
}

void TJsonValue::Clear() noexcept {

    switch (Type) {
    case JSON_STRING:
        delete Value.String;
        break;
    case JSON_MAP:
        delete Value.Map;
        break;
    case JSON_ARRAY:
        delete Value.Array;
        break;
    case JSON_UNDEFINED:
    case JSON_NULL:
    case JSON_BOOLEAN:
    case JSON_INTEGER:
    case JSON_UINTEGER:
    case JSON_DOUBLE:
        break;

    }
    Zero(Value);
    Type = JSON_UNDEFINED;
}

TJsonValue &TJsonValue::operator[] (const size_t idx) {
    SetType(JSON_ARRAY);
    if (Value.Array->size() <= idx)
        Value.Array->resize(idx + 1);
    return (*Value.Array)[idx];
}

TJsonValue &TJsonValue::operator[] (const TStringBuf& key) {
    SetType(JSON_MAP);
    return (*Value.Map)[key];
}

namespace {
    struct TDefaultsHolder {
        const TString String;
        const TJsonValue::TMap Map;
        const TJsonValue::TArray Array;
        const TJsonValue Value;
    };
}

const TJsonValue &TJsonValue::operator[] (const size_t idx) const noexcept {
    const TJsonValue* ret = nullptr;
    if (GetValuePointer(idx, &ret))
        return *ret;

    return Singleton<TDefaultsHolder>()->Value;
}

const TJsonValue &TJsonValue::operator[] (const TStringBuf& key) const noexcept {
    const TJsonValue* ret = nullptr;
    if (GetValuePointer(key, &ret))
        return *ret;

    return Singleton<TDefaultsHolder>()->Value;
}

bool TJsonValue::GetBoolean() const {
    return Type != JSON_BOOLEAN ? false : Value.Boolean;
}

long long TJsonValue::GetInteger() const {
    if (!IsInteger())
        return 0;

    switch (Type) {
    case JSON_INTEGER:
        return Value.Integer;

    case JSON_UINTEGER:
        return Value.UInteger;

    case JSON_DOUBLE:
        return Value.Double;

    default:
        Y_ASSERT(false && "Unexpected type.");
        return 0;
    }
}

unsigned long long TJsonValue::GetUInteger() const {
    if (!IsUInteger())
        return 0;

    switch (Type) {
    case JSON_UINTEGER:
        return Value.UInteger;

    case JSON_INTEGER:
        return Value.Integer;

    case JSON_DOUBLE:
        return Value.Double;

    default:
        Y_ASSERT(false && "Unexpected type.");
        return 0;
    }
}

double TJsonValue::GetDouble() const {
    if (!IsDouble())
        return 0.0;

    switch (Type) {
    case JSON_DOUBLE:
        return Value.Double;

    case JSON_INTEGER:
        return Value.Integer;

    case JSON_UINTEGER:
        return Value.UInteger;

    default:
        Y_ASSERT(false && "Unexpected type.");
        return 0.0;
    }
}

const TString& TJsonValue::GetString() const {
    return Type != JSON_STRING ? Singleton<TDefaultsHolder>()->String : *Value.String;
}

const TJsonValue::TMap& TJsonValue::GetMap() const {
    return Type != JSON_MAP ? Singleton<TDefaultsHolder>()->Map : *Value.Map;
}

const TJsonValue::TArray &TJsonValue::GetArray() const {
    return (Type != JSON_ARRAY) ? Singleton<TDefaultsHolder>()->Array : *Value.Array;
}

bool TJsonValue::GetBooleanSafe() const {
    if (Type != JSON_BOOLEAN)
        ythrow TJsonException() << "Not a boolean";

    return Value.Boolean;
}

long long TJsonValue::GetIntegerSafe() const {
    if (!IsInteger())
        ythrow TJsonException() << "Not an integer";

    return GetInteger();
}

unsigned long long TJsonValue::GetUIntegerSafe() const {
    if (!IsUInteger())
        ythrow TJsonException() << "Not an unsigned integer";

    return GetUInteger();
}

double TJsonValue::GetDoubleSafe() const {
    if (!IsDouble())
        ythrow TJsonException() << "Not a double";

    return GetDouble();
}

const TString& TJsonValue::GetStringSafe() const {
    if (Type != JSON_STRING)
        ythrow TJsonException() << "Not a string";

    return *Value.String;
}

bool TJsonValue::GetBooleanSafe(const bool defaultValue) const {
    if (Type == JSON_UNDEFINED)
        return defaultValue;

    return GetBooleanSafe();
}

long long TJsonValue::GetIntegerSafe(const long long defaultValue) const {
    if (Type == JSON_UNDEFINED)
        return defaultValue;

    return GetIntegerSafe();
}

unsigned long long TJsonValue::GetUIntegerSafe(const unsigned long long defaultValue) const {
    if (Type == JSON_UNDEFINED)
        return defaultValue;

    return GetUIntegerSafe();
}

double TJsonValue::GetDoubleSafe(const double defaultValue) const {
    if (Type == JSON_UNDEFINED)
        return defaultValue;

    return GetDoubleSafe();
}

TString TJsonValue::GetStringSafe(const TString& defaultValue) const {
    if (Type == JSON_UNDEFINED)
        return defaultValue;

    return GetStringSafe();
}

const TJsonValue::TMap& TJsonValue::GetMapSafe() const {
    if (Type != JSON_MAP)
        ythrow TJsonException() << "Not a map";

    return *Value.Map;
}

TJsonValue::TMap& TJsonValue::GetMapSafe() {
    return const_cast<TJsonValue::TMap&>(const_cast<const TJsonValue*>(this)->GetMapSafe());
}

const TJsonValue::TArray& TJsonValue::GetArraySafe() const {
    if (Type != JSON_ARRAY)
        ythrow TJsonException() << "Not an array";

    return *Value.Array;
}

TJsonValue::TArray& TJsonValue::GetArraySafe() {
    return const_cast<TJsonValue::TArray&>(const_cast<const TJsonValue*>(this)->GetArraySafe());
}

bool TJsonValue::GetBooleanRobust() const noexcept {
    switch (Type) {
    case JSON_ARRAY:
        return !Value.Array->empty();
    case JSON_MAP:
        return !Value.Map->empty();
    case JSON_INTEGER:
    case JSON_UINTEGER:
    case JSON_DOUBLE:
        return GetIntegerRobust();
    case JSON_STRING:
        return GetIntegerRobust() || IsTrue(*Value.String);
    case JSON_NULL:
    case JSON_UNDEFINED:
    default:
        return 0;
    case JSON_BOOLEAN:
        return Value.Boolean;
    }
}

long long TJsonValue::GetIntegerRobust() const noexcept {
    switch (Type) {
    case JSON_ARRAY:
        return Value.Array->size();
    case JSON_MAP:
        return Value.Map->size();
    case JSON_BOOLEAN:
        return Value.Boolean;
    case JSON_DOUBLE:
        return GetDoubleRobust();
    case JSON_STRING:
        try {
            i64 res = 0;
            if (Value.String && TryFromString(*Value.String, res)) {
                return res;
            }
        } catch (const yexception&) {}
        return 0;
    case JSON_NULL:
    case JSON_UNDEFINED:
    default:
        return 0;
    case JSON_INTEGER:
    case JSON_UINTEGER:
        return Value.Integer;
    }
}

unsigned long long TJsonValue::GetUIntegerRobust() const noexcept {
    switch (Type) {
    case JSON_ARRAY:
        return Value.Array->size();
    case JSON_MAP:
        return Value.Map->size();
    case JSON_BOOLEAN:
        return Value.Boolean;
    case JSON_DOUBLE:
        return GetDoubleRobust();
    case JSON_STRING:
        try {
            ui64 res = 0;
            if (Value.String && TryFromString(*Value.String, res)) {
                return res;
            }
        } catch (const yexception&) {}
        return 0;
    case JSON_NULL:
    case JSON_UNDEFINED:
    default:
        return 0;
    case JSON_INTEGER:
    case JSON_UINTEGER:
        return Value.UInteger;
    }
}

double TJsonValue::GetDoubleRobust() const noexcept {
    switch (Type) {
    case JSON_ARRAY:
        return Value.Array->size();
    case JSON_MAP:
        return Value.Map->size();
    case JSON_BOOLEAN:
        return Value.Boolean;
    case JSON_INTEGER:
        return Value.Integer;
    case JSON_UINTEGER:
        return Value.UInteger;
    case JSON_STRING:
        try {
            double res = 0;
            if (Value.String && TryFromString(*Value.String, res)) {
                return res;
            }
        } catch (const yexception&) {}
        return 0;
    case JSON_NULL:
    case JSON_UNDEFINED:
    default:
        return 0;
    case JSON_DOUBLE:
        return Value.Double;
    }
}

TString TJsonValue::GetStringRobust() const {
    switch (Type) {
    case JSON_ARRAY:
    case JSON_MAP:
    case JSON_BOOLEAN:
    case JSON_DOUBLE:
    case JSON_INTEGER:
    case JSON_UINTEGER:
    case JSON_NULL:
    case JSON_UNDEFINED:
    default: {
        NJsonWriter::TBuf sout;
        sout.WriteJsonValue(this);
        return sout.Str();
    } case JSON_STRING:
        return *Value.String;
    }
}

bool TJsonValue::GetBoolean(bool *value) const noexcept {
    if (Type != JSON_BOOLEAN)
        return false;

    *value = Value.Boolean;
    return true;
}

bool TJsonValue::GetInteger(long long *value) const noexcept {
    if (!IsInteger())
        return false;

    *value = GetInteger();
    return true;
}

bool TJsonValue::GetUInteger(unsigned long long *value) const noexcept {
    if (!IsUInteger())
        return false;

    *value = GetUInteger();
    return true;
}

bool TJsonValue::GetDouble(double *value) const noexcept {
    if (!IsDouble())
        return false;

    *value = GetDouble();
    return true;
}

bool TJsonValue::GetString(TString *value) const {
    if (Type != JSON_STRING)
        return false;

    *value = *Value.String;
    return true;
}

bool TJsonValue::GetMap(TJsonValue::TMap *value) const {
    if (Type != JSON_MAP)
        return false;

    *value = *Value.Map;
    return true;
}

bool TJsonValue::GetArray(TJsonValue::TArray *value) const {
    if (Type != JSON_ARRAY)
        return false;

    *value = *Value.Array;
    return true;
}

bool TJsonValue::GetMapPointer(const TJsonValue::TMap **value) const noexcept {
    if (Type != JSON_MAP)
        return false;

    *value = Value.Map;
    return true;
}

bool TJsonValue::GetArrayPointer(const TJsonValue::TArray **value) const noexcept {
    if (Type != JSON_ARRAY)
        return false;

    *value = Value.Array;
    return true;
}


bool TJsonValue::GetValue(const size_t index, TJsonValue *value) const {
    const TJsonValue* tmp = nullptr;
    if (GetValuePointer(index, &tmp)) {
        *value = *tmp;
        return true;
    }
    return false;
}

bool TJsonValue::GetValue(const TStringBuf key, TJsonValue *value) const {
    const TJsonValue* tmp = nullptr;
    if (GetValuePointer(key, &tmp)) {
        *value = *tmp;
        return true;
    }
    return false;
}

bool TJsonValue::GetValuePointer(const size_t index, const TJsonValue **value) const noexcept {
    if (Type == JSON_ARRAY && index < Value.Array->size()) {
        *value = &(*Value.Array)[index];
        return true;
    }
    return false;
}

bool TJsonValue::GetValuePointer(const TStringBuf key, const TJsonValue **value) const noexcept {
    if (Type == JSON_MAP) {
        const TMap::const_iterator it = Value.Map->find(key);
        if (it != Value.Map->end()) {
            *value = &(it->second);
            return true;
        }
    }
    return false;
}

bool TJsonValue::GetValuePointer(const TStringBuf key, TJsonValue **value) noexcept {
    return static_cast<const TJsonValue*>(this)->GetValuePointer(key, const_cast<const TJsonValue**>(value));
}

bool TJsonValue::IsNull() const noexcept {
    return Type == JSON_NULL;
}

bool TJsonValue::IsBoolean() const noexcept {
    return Type == JSON_BOOLEAN;
}

bool TJsonValue::IsInteger() const noexcept {
    switch (Type) {
    case JSON_INTEGER:
        return true;

    case JSON_UINTEGER:
        return (Value.UInteger <= static_cast<unsigned long long>(Max<long long>()));

    case JSON_DOUBLE:
        return ((long long)Value.Double == Value.Double);

    default:
        return false;
    }
}

bool TJsonValue::IsUInteger() const noexcept {
    switch (Type) {
    case JSON_UINTEGER:
        return true;

    case JSON_INTEGER:
        return (Value.Integer >= 0);

    case JSON_DOUBLE:
        return ((unsigned long long)Value.Double == Value.Double);

    default:
        return false;
    }
}

bool TJsonValue::IsDouble() const noexcept {
    // Check whether we can convert integer to floating-point
    // without precision loss.
    switch (Type) {
    case JSON_DOUBLE:
        return true;

    case JSON_INTEGER:
        return ((long long)(double)Value.Integer == Value.Integer);

    case JSON_UINTEGER:
        return ((unsigned long long)(double)Value.UInteger == Value.UInteger);

    default:
        return false;
    }
}

namespace {
    template <class TPtr, class T>
    TPtr* CreateOrNullptr(TPtr* p, T key, std::true_type /*create*/) {
        return &(*p)[key];
    }

    template <class TPtr, class T>
    TPtr* CreateOrNullptr(const TPtr* p, T key, std::false_type /*create*/) noexcept {
        const TPtr* const next = &(*p)[key];
        return next->IsDefined() ? const_cast<TPtr*>(next) : nullptr;
    }

    template <bool Create, class TJsonPtr>
    TJsonPtr GetValuePtrByPath(TJsonPtr currentJson, TStringBuf path, char delimiter) noexcept(!Create) {
        static_assert(
            !(Create && std::is_const< std::remove_pointer_t<TJsonPtr> >::value),
            "TJsonPtr must be a `TJsonValue*` if `Create` is true"
        );
        constexpr std::integral_constant<bool, Create> create_tag{};

        while (!path.empty()) {
            size_t index = 0;
            const TStringBuf step = path.NextTok(delimiter);
            if (step.size() > 2 && *step.begin() == '[' && step.back() == ']' && TryFromString(step.substr(1, step.size() - 2), index)) {
                currentJson = CreateOrNullptr(currentJson, index, create_tag);
            } else {
                currentJson = CreateOrNullptr(currentJson, step, create_tag);
            }

            if (!currentJson) {
                return nullptr;
            }
        }

        return currentJson;
    }
} // anonymous namespace

bool TJsonValue::GetValueByPath(const TStringBuf path, TJsonValue& result, char delimiter) {
    TJsonValue* const ptr = GetValuePtrByPath<false>(this, path, delimiter);
    if (ptr) {
        result = *ptr;
        return true;
    }
    return false;
}

bool TJsonValue::SetValueByPath(const TStringBuf path, const TJsonValue& value, char delimiter) {
    TJsonValue* const ptr = GetValuePtrByPath<true>(this, path, delimiter);
    if (ptr) {
        *ptr = value;
        return true;
    }
    return false;
}

bool TJsonValue::SetValueByPath(const TStringBuf path, TJsonValue&& value, char delimiter) {
    TJsonValue* const ptr = GetValuePtrByPath<true>(this, path, delimiter);
    if (ptr) {
        *ptr = std::move(value);
        return true;
    }
    return false;
}

const TJsonValue* TJsonValue::GetValueByPath(const TStringBuf key, char delim) const noexcept {
    return GetValuePtrByPath<false>(this, key, delim);
}

TJsonValue* TJsonValue::GetValueByPath(const TStringBuf key, char delim) noexcept {
    return GetValuePtrByPath<false>(this, key, delim);
}

void TJsonValue::DoScan(const TString& path, TJsonValue* parent, IScanCallback& callback) {
    if (!callback.Do(path, parent, *this)) {
        return;
    }

    if (Type == JSON_MAP) {
        for (auto&& i : *Value.Map) {
            i.second.DoScan((!!path ? path + "." : "") + i.first, this, callback);
        }
    } else if (Type == JSON_ARRAY) {
        for (ui32 i = 0; i < Value.Array->size(); ++i) {
            (*Value.Array)[i].DoScan(path + "[" + ToString(i) + "]", this, callback);
        }
    }
}

void TJsonValue::Scan(IScanCallback& callback) {
    DoScan("", nullptr, callback);
}

bool TJsonValue::IsString() const noexcept {
    return Type == JSON_STRING;
}

bool TJsonValue::IsMap() const noexcept {
    return Type == JSON_MAP;
}

bool TJsonValue::IsArray() const noexcept {
    return Type == JSON_ARRAY;
}

bool TJsonValue::Has(const TStringBuf& key) const noexcept {
    return Type == JSON_MAP && Value.Map->has(key);
}

bool TJsonValue::Has(size_t key) const noexcept {
    return Type == JSON_ARRAY && Value.Array->size() > key;
}

bool TJsonValue::operator== (const TJsonValue& rhs) const {
    switch (Type) {
    case JSON_UNDEFINED: {
        return (rhs.GetType() == JSON_UNDEFINED);
    }

    case JSON_NULL: {
        return rhs.IsNull();
    }

    case JSON_BOOLEAN: {
        return (rhs.IsBoolean() && Value.Boolean == rhs.Value.Boolean);
    }

    case JSON_INTEGER: {
        return (rhs.IsInteger()
                && GetInteger() == rhs.GetInteger());
    }

    case JSON_UINTEGER: {
        return (rhs.IsUInteger()
                && GetUInteger() == rhs.GetUInteger());
    }

    case JSON_STRING: {
        return (rhs.IsString()
                && !!Value.String && !!rhs.Value.String
                && *Value.String == *rhs.Value.String);
    }

    case JSON_DOUBLE: {
        return (rhs.IsDouble()
                && fabs(GetDouble() - rhs.GetDouble()) <= FLT_EPSILON);
    }

    case JSON_MAP:
        return AreJsonMapsEqual(*this, rhs);

    case JSON_ARRAY:
        return AreJsonArraysEqual(*this, rhs);

    default:
        Y_ASSERT(false && "Unknown type.");
        return false;
    }
}

void TJsonValue::Swap(TJsonValue& rhs) noexcept {
    ::DoSwap(Type, rhs.Type);

    // DoSwap does not support unions
    TValueUnion tmp = Value;
    Value = rhs.Value;
    rhs.Value = tmp;
}

//****************************************************************


bool GetMapPointer(const TJsonValue &jv, const size_t index, const TJsonValue::TMap **value) {
    const TJsonValue* v;
    if (!jv.GetValuePointer(index, &v) || !v->IsMap())
        return false;

    *value = &v->GetMap();
    return true;
}

bool GetArrayPointer(const TJsonValue &jv, const size_t index, const TJsonValue::TArray **value) {
    const TJsonValue* v;
    if (!jv.GetValuePointer(index, &v) || !v->IsArray())
        return false;

    *value = &v->GetArray();
    return true;
}


bool GetMapPointer(const TJsonValue &jv, const TStringBuf key, const TJsonValue::TMap **value) {
    const TJsonValue *v;
    if (!jv.GetValuePointer(key, &v) || !v->IsMap())
        return false;

    *value = &v->GetMap();
    return true;
}

bool GetArrayPointer(const TJsonValue &jv, const TStringBuf key, const TJsonValue::TArray **value) {
    const TJsonValue *v;
    if (!jv.GetValuePointer(key, &v) || !v->IsArray())
        return false;

    *value = &v->GetArray();
    return true;
}

}

template <>
void Out<NJson::TJsonValue>(TOutputStream& out, const NJson::TJsonValue& v) {
    NJsonWriter::TBuf buf(NJsonWriter::HEM_DONT_ESCAPE_HTML, &out);
    buf.WriteJsonValue(&v);
}
